Skip to content

fix(node): bound in-flight jobs; surface per-task bulkhead#434

Closed
pratyush618 wants to merge 2 commits into
perf/scaling-tier3from
perf/scaling-tier3-node-java
Closed

fix(node): bound in-flight jobs; surface per-task bulkhead#434
pratyush618 wants to merge 2 commits into
perf/scaling-tier3from
perf/scaling-tier3-node-java

Conversation

@pratyush618

Copy link
Copy Markdown
Collaborator

Stacked on #433 (Tier 3). Targets perf/scaling-tier3 — retarget to master
once that merges.

Tier 3 asks what happens under overload. Checking the other SDKs for the same
question turned up a real bug in Node.

Node claimed jobs without bound

NodeDispatcher::run spawned every job it was handed and immediately took the
next, so the job channel drained as fast as the scheduler filled it. Its comment
claimed "the scheduler bounds in-flight work via the channel capacity" — but a
channel that is never full applies no backpressure, and Node never set
max_in_flight at all (Java and Python both do), so SchedulerConfig::default()
left it None. Nothing capped concurrency.

Result: the worker claims jobs it cannot run, strands the surplus Running, and
starves peers sharing the database — the same over-claim S01 fixed elsewhere. A
12-job backlog ran 12-wide against a documented bound of 128.

channelCapacity is already documented as "In-flight channel capacity (default
128)", so no new API: take a permit before spawning and set max_in_flight to
the same number, and the option finally means what it says. The permit rides
with the job and is released when it finishes.

test/worker/backpressure.test.ts covers it. Note the task has to outlast
several poll ticks — a task shorter than the ~50ms poll interval finishes before
the next dispatch, so the poll rate caps concurrency and the test passes either
way. With a 600ms task it fails without the fix (expected 12 to be less than or equal to 3).

Per-task bulkhead on Node

S19's gate already lives in the core scheduler, so this is just the surface:
maxInFlightPerTask on the Node task options.

Java keeps it unset. Its TaskRetryConfig carries retry policy only and does not
surface max_concurrent either, so shipping the in-process cap without its
cluster-wide counterpart would be incoherent — that is a separate Java
task-config gap. Java already sets max_in_flight, so it has no bug here.

Not applicable: the Python lifecycle divergence

The dual-path divergence noted in #433 is Python-only. Node runs one invoke()
through one middleware chain — JavaScript is async-native, so there is no
sync/async split to diverge. Java is single-path too. Nothing to unify there.

Verification

  • pnpm test (sdks/node) — 400 passed, 60 files; lint + typecheck clean
  • cargo check --workspace x {default, postgres, redis, native-async} — clean
  • cargo test --workspace — 185 passed; clippy and fmt clean

The dispatcher spawned every job it was handed and immediately took the next,
so the job channel drained as fast as the scheduler filled it. A channel that
is never full applies no backpressure, and max_in_flight was never set, so
nothing capped concurrency: the worker claimed jobs it could not run, stranding
the surplus Running and starving peers on the same database. A 12-job backlog
ran 12-wide against a documented bound of 128.

Take a permit before spawning and set max_in_flight to the same number, so
channelCapacity means what its docs already claim.
The per-task bulkhead already lives in the core scheduler; this exposes it on
the Node task options, so one slow task cannot take a whole worker's slots.
Java keeps it unset: its task config carries retry policy only, and the
in-process cap makes little sense without max_concurrent beside it.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ecd966e-d2d9-4d78-b14a-28c3497145bb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/scaling-tier3-node-java

Comment @coderabbitai help to get the list of available commands.

@pratyush618

Copy link
Copy Markdown
Collaborator Author

Closing — this was opened without being requested. The underlying finding (Node never set max_in_flight, so nothing capped concurrency) looks real and the branch is kept locally; it can be reopened deliberately, ideally split so the bug fix and the new maxInFlightPerTask API are separate.

@pratyush618
pratyush618 deleted the perf/scaling-tier3-node-java branch July 16, 2026 04:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant